﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Security.AccessControl;
using System.Security.Principal;

namespace Umbrella.Whs
{
    public sealed class GlobalMutex : IDisposable
    {
        private static readonly MutexSecurity _security;
        private Mutex _mutex;

        public interface ILockUsing : IDisposable
        {
            bool IsLocked
            {
                get;
            }
        }

        public class ForceLockUsing : ILockUsing
        {
            private GlobalMutex _gm;
            private bool _isLocked = false;

            public bool IsLocked
            {
                get { return _isLocked; }
            }

            internal ForceLockUsing(GlobalMutex gm)
            {
                if (gm == null)
                    throw new ArgumentNullException("gm");

                _gm = gm;

                _gm._mutex.WaitOne();

                _isLocked = true;
            }

            public void Dispose()
            {
                if (_gm != null)
                    _gm._mutex.ReleaseMutex();

                _isLocked = false;
            }

        }

        public class TryLockUsing : ILockUsing
        {
            private GlobalMutex _gm;
            private bool _isLocked;

            public bool IsLocked
            {
                get { return _isLocked; }
            }

            internal TryLockUsing(GlobalMutex gm, TimeSpan time)
            {
                if (gm == null)
                    throw new ArgumentNullException("gm");

                _gm = gm;

                _isLocked = _gm._mutex.WaitOne(time);
            }

            public void Dispose()
            {
                if ((_gm != null) && _isLocked)
                    _gm._mutex.ReleaseMutex();

                _isLocked = false;
            }

        }

        /// <summary>
        /// used to force locking over a specific part
        /// 
        /// to be used in conjunction with using() {}
        /// </summary>
        public ForceLockUsing ForceLock
        {
            get { return new ForceLockUsing(this); }
        }

        /// <summary>
        /// tries to lock for a specific amount of time.
        /// if not locking was not successful during that time
        /// period the locking failed (return.IsLocked=false).
        /// 
        /// to be used in conjunction with using() {}
        /// </summary>
        public TryLockUsing TryLock(TimeSpan tryFor)
        {
            return new TryLockUsing(this, tryFor);
        }

        /// <summary>
        /// tries to lock for a specific amount of time.
        /// if not locking was not successful during that time
        /// period the locking failed (return.IsLocked=false).
        /// 
        /// to be used in conjunction with using() {}
        /// </summary>
        public TryLockUsing TryLock(int tryForMilliSeconds)
        {
            return new TryLockUsing(this, new TimeSpan(TimeSpan.TicksPerMillisecond * (long)tryForMilliSeconds));
        }

        public void Dispose()
        {
            if (_mutex != null)
            {
                try
                {
                    _mutex.ReleaseMutex();
                }
                catch (ApplicationException)
                {
                    // ignore if not owned
                }
            }
        }

        static GlobalMutex()
        {
            var user = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
            var security = new MutexSecurity();

            // allow sync
            {
                var rule = new MutexAccessRule(
                    user,
                    MutexRights.Synchronize | MutexRights.TakeOwnership,
                    AccessControlType.Allow
                    );

                security.AddAccessRule(rule);
            }
			
            // disallow permission change
            {
                var rule = new MutexAccessRule(
                    user,
                    MutexRights.ChangePermissions,
                    AccessControlType.Deny
                    );

                security.AddAccessRule(rule);
            }

            // done
            _security = security;
        }

        public GlobalMutex(Guid id)
        {
            var mutexName = ("Global\\" + id.ToString("N"));

            // try to open existing
            try
            {
                _mutex = Mutex.OpenExisting(mutexName, MutexRights.Synchronize | MutexRights.TakeOwnership);
            }
            catch (WaitHandleCannotBeOpenedException)
            {
                bool isNew;

                _mutex = new Mutex(
                    true,
                    mutexName,
                    out isNew,
                    _security
                    );
            }

        }

    }
}